#include "StdAfx.h"

#include <wx/filename.h>
#ifdef WIN32
# include <windows.h>
#endif
#include <malloc.h>
#include <algorithm>

#include "utils/Macros.h"
#include "utils/Errors.h"
#include "utils/Files.h"
#include "TagStorage.h"
#include "readtags.h"
#include "TagPreferences.h"

TagStorage tagStorage;

TagStorage::TagStorage()
{

}

void TagStorage::clear()
{
	for(int i = 0; i < TAG_TYPE_COUNT; ++i)
		sortedTags_[i].clear();
	sources_.clear();
}

void TagStorage::addSource(TagSource* newSource)
{
	sources_.push_back(newSource);
}


void TagStorage::sortedInsert(SortedTags& sortedTags, const Tag* tagToInsert)
{
	SortedTags::iterator it = std::lower_bound(sortedTags.begin(), sortedTags.end(), tagToInsert, TagCompare());
	sortedTags.insert(it, tagToInsert);
}

void TagStorage::addTagsFromNode(TagNode* node)
{
	for(int i = 0; i < TAG_TYPE_COUNT; ++i){
		TagType type = (TagType)(i);
		SortedTags& sortedTags = sortedTags_[i];
		const TagNode::Tags& tags = node->tags(type);
		TagNode::Tags::const_iterator it;
		for(it = tags.begin(); it != tags.end(); ++it){
			const Tag* tag = &*it;
			sortedInsert(sortedTags, tag);
			sortedInsert(newTags_, tag);
		}
	}
}

void TagStorage::removeTagsByNode(TagNode* node)
{
	for(int i = 0; i < TAG_TYPE_COUNT; ++i){
		TagType type = (TagType)(i);
		SortedTags& sortedTags = sortedTags_[i];

		TagNode::Tags::const_iterator it;
		const TagNode::Tags& tags = node->tags(type);
		for(it = tags.begin(); it != tags.end(); ++it){
			const Tag* tag = &*it;

			SortedTags::iterator sit = std::find(sortedTags.begin(), sortedTags.end(), tag);
			if(sit != sortedTags.end()){
				sortedTags.erase(sit);
				deadTags_.push_back(tag);
			}

			NewTags::iterator nit = std::find(newTags_.begin(), newTags_.end(), tag);
			if(nit != newTags_.end()){
				newTags_.erase(nit);
			}
		}		
	}
}

bool TagStorage::pollSources()
{
	bool changed = false;
	Sources::iterator it;
	FOR_EACH(sources_, it){
		TagSource* source = *it;
		if(source->poll())
			changed = true;
	}
	return changed;
}

void updateTagsFile(const char* tagsFileName, const char* sourceFileName)
{
#ifdef WIN32
	std::string tagDirectory = Files::extractFilePath(tagsFileName);
	std::string commandLine = "ctags.exe --file-scope=yes --excmd=number --c++-kinds=+p --fields=afikmnSzts --sort=no -a ";
	commandLine += sourceFileName;

	PROCESS_INFORMATION processInfo;
	ZeroMemory(&processInfo, sizeof(processInfo));
	
	STARTUPINFO startupInfo;
	ZeroMemory(&startupInfo, sizeof(startupInfo));

	char* commandLineBuffer = (char*)alloca(commandLine.size() + 1);
	strcpy(commandLineBuffer, commandLine.c_str());
	CreateProcess(0, commandLineBuffer, 0, 0, FALSE, CREATE_NO_WINDOW, 0, tagDirectory.c_str(), &startupInfo, &processInfo);
	
	

	WaitForSingleObject(processInfo.hProcess, INFINITE);
	CloseHandle(processInfo.hProcess);
	CloseHandle(processInfo.hThread);
#else

#endif
}

// ---------------------------------------------------------------------------

void TagNode::clear()
{
	for(int i = 0; i < TAG_TYPE_COUNT; ++i)
		tags_[i].clear();
}

const char* TagNode::addConstString(const char* str)
{
	strings_.push_back(str);
	return strings_.back().c_str();
}

TagNode::TagNode(TagStorage* storage)
: storage_(storage)
, parent_(0)
{
}

void TagNode::add(TagNode* newChild)
{
	children_.push_back(newChild);
	newChild->setParent(this);
}

void TagNode::remove(TagNode* node)
{
	Children::iterator it = std::find(children_.begin(), children_.end(), node);
	if(it != children_.end()){
		node->setParent(0);
		children_.erase(it);
	}
	else
		ASSERT(0 && "No such node");
}

void TagNode::setParent(TagNode* newParent)
{
	parent_ = newParent;
}

TagNode* TagNode::findByName(const char* name)
{
	Children::iterator it;
	FOR_EACH(children_, it){
		TagNode* child = *it;
		if(strcmp(child->name(), name) == 0)
			return child;
	}
	return 0;
}

bool TagNode::loadFromFile(const char* tagFileName)
{
	clear();

	tagFileInfo info;
	tagEntry entry;
	tagFile* file = tagsOpen(tagFileName, &info);

	if(file == 0){
		return false;
		//RAISE(ErrorRuntime("Unable to tagsOpen..."));
	}

	int index = 0;
	while(tagsNext(file, &entry) == TagSuccess){
		Tag tag;
		tag.location.fileName = addConstString(entry.file);
		tag.location.line = entry.address.lineNumber;
		tag.name = tag.nameFull = tag.title = tag.titleFull = addConstString(entry.name);
		switch(entry.kind[0]){
		case 'm':
		case 'v':
			tag.type = TAG_MEMBER;
			break;
		case 't':
			tag.type = TAG_TYPEDEF;
			break;
		case 's':
		case 'c':
			tag.type = TAG_CLASS;
			break;
		case 'd':
			tag.type = TAG_DEFINE;
			break;
		case 'f':
			tag.type = TAG_FUNCTION;
			break;
		case 'p':
			tag.type = TAG_PROTOTYPE;
			break;
		case 'e':
			tag.type = TAG_ENUM_VALUE;
			break;
		case 'g':
			tag.type = TAG_ENUM;
			break;
		default:
			tag.type = TAG_UNKNOWN;
			break;
		}
		//int index = tags_[tag.type].size();

		const char* namespace_name = 0;
		const char* class_name = 0;
		const char* enum_name = 0;
		const char* signature = 0;

		for(int i = 0; i < entry.fields.count; ++i){
			const char* key = entry.fields.list[i].key;
			const char* value = entry.fields.list[i].value;
			if(strcmp(key, "namespace") == 0){
				namespace_name = value;
			}
			else if(strcmp(key, "class") == 0 || strcmp(key, "struct") == 0){
				class_name = value;
			}
			else if(strcmp(key, "enum") == 0){
				enum_name = value;
			}
			else if(strcmp(key, "signature") == 0){
				signature = value;
				tag.signature = addConstString(signature);
			}
		}

		if(tag.type == TAG_CLASS || tag.type == TAG_ENUM && (namespace_name || class_name || enum_name)){
			std::string fullName;
			if(namespace_name){
				fullName += namespace_name;
				fullName += "::";
			}
			if(class_name){
				fullName += class_name;
				tag.parentClass = addConstString(fullName.c_str());
				fullName += "::";

			}
			if(enum_name){
				fullName += enum_name;
				tag.parentClass = addConstString(fullName.c_str());
				fullName += "::";
			}
			fullName += tag.name;
			tag.nameFull = addConstString(fullName.c_str());
		}
		else
			tag.nameFull = tag.name;
		
		// title
		if(tag.type != TAG_CLASS && tag.type != TAG_ENUM){
			if(signature){
				std::string title = tag.name;
				title += " ";
				title += signature;
				tag.title = addConstString(title.c_str());
			}
			else
				tag.title = tag.nameFull;

			if(signature || class_name || enum_name){
				std::string title = tag.nameFull;
				if(signature){
					title += " ";
					title += signature;
				}

				title += "  -  ";
				if(class_name){
					title += class_name;
					tag.parentClass = addConstString(class_name);
					ASSERT(enum_name == 0);
				}
				if(enum_name){
					ASSERT(class_name == 0);
					title += enum_name;
					tag.parentClass = addConstString(enum_name);
				}
				tag.titleFull = addConstString(title.c_str());
			}
			else{
				tag.titleFull = tag.title;
			}
		}
		else{
			tag.title = tag.name;
			tag.titleFull = tag.nameFull;
		}		

		// class
		tags_[tag.type].push_back(tag);
	}
	tagsClose(file);
	return true;
}

bool TagNode::parseSource(const char* sourceFileName)
{
	STARTUPINFO startupInfo;
	GetStartupInfo(&startupInfo);

	/*
	SECURITY_DESCRIPTOR securityDescriptor;
	InitializeSecurityDescriptor(&securityDescriptor, SECURITY_DESCRIPTOR_REVISION);
	SetSecurityDescriptorDacl(&securityDescriptor, TRUE, 0, FALSE);

	SECURITY_ATTRIBUTES securityAttribs;
	securityAttribs.lpSecurityDescriptor = &securityDescriptor;
	securityAttribs.nLength = sizeof(securityAttribs);
	securityAttribs.bInheritHandle = TRUE;

	if(!CreatePipe(
	*/


	// temproary solution. whall be implemented with pipes
	std::string tempFileName = wxFileName::CreateTempFileName(wxString("vimkick")).utf8_str();

	std::string commandLine = tagPreferences.ctagsExecutable();
	commandLine += " --file-scope=yes --excmd=number --c++-kinds=+p --fields=afikmnSzts --sort=no -f \"";
	commandLine += tempFileName;
	commandLine += "\" \"";
	commandLine += sourceFileName;
	commandLine += "\"";

	PROCESS_INFORMATION processInfo;
	ZeroMemory(&processInfo, sizeof(processInfo));
	
	char* commandLineBuffer = (char*)alloca(commandLine.size() + 1);
	strcpy(commandLineBuffer, commandLine.c_str());
	CreateProcess(0, commandLineBuffer, 0, 0, FALSE, CREATE_NO_WINDOW, 0, 0, &startupInfo, &processInfo);
	
	WaitForSingleObject(processInfo.hProcess, INFINITE);
	CloseHandle(processInfo.hProcess);
	CloseHandle(processInfo.hThread);
	
	bool result = loadFromFile(tempFileName.c_str());;
	Files::remove(tempFileName.c_str());
	return result;
}
